/* Arduino Uno SSTV PD90 QR encoder using a VS1003-based audio module
 * --
 * Chris Baird, August 2022 <vk2cjb@gmail.com>
 * Usage as per the GPLv3.
 */
/* based on vs1003_hello created 2012-04-05 by Andrey Karpov
 *
 * VS1003 development board connected thusly to the Arduino Mega:
 * [GND, 5V]
 * XDCS - D6
 * DREQ - D7
 * XRES - D8
 * XCS  - D9
 * SI   - D11
 * SO   - D12
 * SCLK - D13
 */

#include <math.h>		/* for fmodf() */
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <Arduino.h>
#include "VS1003.h"
#include <SPI.h>
#include "sine.h"
#include "qrcode.h"

// QRMODE, matrix size, max message length for each ECC level (0-3)
// V1: 21x21 : 17,14,11,7
// V2: 25x25 : 32,26,20,14
// V3: 29x29 : 53,42,32,24
#define QRMODE 2
#define QRECC 3
#define QRMESSAGE "VK2CJB QF57PC"

QRCode qrcode;
uint8_t qrcodeData[106];

#if QRMODE == 2
#define SCREEN 28
#endif
#if QRMODE == 1
#define SCREEN 24
#endif

VS1003 player(9, 6, 7, 8); // cs_pin, dcs_pin, dreq_pin, reset_pin

#define VISCODE (99)            // PD90
#define scanlen (1362)          // 170.240 ms in audio samples

uint16_t text_on = 2300, text_off = 1500;

#define samplebytes (2)
#define channels (1)
#define samplerate (8000)
#define datarate (samplerate*channels*samplebytes)
#define TONESIZE (16)	      // using anything else causes distortion
uint16_t tonebuf[TONESIZE];

const uint8_t wavheader[] =
  {
    'R','I','F','F',
    0,0,0,0,                     // wav size (0=play indefinitely)
    'W','A','V','E', 'f','m','t',' ',
    16, 0, 0, 0,                 // fmt_chunk_size
    1, 0,                        // audio format = PCM
    1, 0,                        // num_channels
    samplerate&255, (samplerate>>8)&255, samplerate>>16,0,
    datarate&255, (datarate>>8)&255, datarate>>16,0,
    samplebytes, 0,              // sample_alignment
    samplebytes<<3, 0,
    'd','a','t','a',
    0,0,0,0                      // data size (0=infinite)
  };

// ----------------------------------------------------------------------

void setup ()
{
  qrcode_initText(&qrcode, qrcodeData, QRMODE, QRECC, QRMESSAGE);

  SPI.begin();
  player.begin();
  player.setVolume(0);
  delay(4500);			// hack to fix power-up drifting
  player.playChunk(wavheader,sizeof(wavheader));
}

// ----------------------------------------------------------------------

void sampleout (uint16_t freq)
{
  static float iphase = 0;
  static int bp = 0;

  iphase += ((float)SINESIZE*(float)freq) / (float)samplerate;
  iphase = fmodf(iphase,SINESIZE);
  tonebuf[bp] = pgm_read_word(&sine[(int)iphase]);
  if (++bp == TONESIZE)
    {
      player.playChunk((const uint8_t*)tonebuf, sizeof(tonebuf));
      bp = 0;
    }
}


void tonegen (float freq, uint16_t samples)
{
  while (samples--) sampleout(freq);
}


void vis(uint8_t c)
{
  uint16_t i, p = 0;

  tonegen(1900,2400);		// 300ms
  tonegen(1200,80);		// 10ms
  tonegen(1900,2400);		// 300ms
  tonegen(1200,240);		// 30ms

  for (i = 1; i != 256; i <<= 1)
    {
      if ((i == 128) && (p & 1)) c |= 128;
      if (c & i)
	{
	  p++;
	  tonegen(1100,240);
	}
      else tonegen(1300,240);
    }
  tonegen(1200,240);
}

// ----------------------------------------------------------------------

void doline(int y)
{
  int y1 = ((y * SCREEN) >> 8) - 2;
  int xt = -2, i;
  uint8_t c = 0, bm = 0, ch;
  uint16_t tf = 0, xx = 0, t;

  for (uint16_t x = 0; x < scanlen; x++)
    {
      if (tf >= 256)
	{
	  bm >>= 1;
	  tf -= 256;
	  xx++;
	}
      tf += ((uint32_t)SCREEN << 11) / scanlen;

      if (bm == 0)
	{
	  if ((xt < 0) || (xt >= qrcode.size) || (y1 < 0) || (y1 >= qrcode.size))
	    c = 0;
	  else
	    c = qrcode_getModule(&qrcode, xt, y1) ? 255 : 0;

	  xt++;
	  bm = 128;
	}

      t = text_on;
      int yy = y << 1;
      if (c & bm) t = text_off;
      sampleout(t);
    }
}

// ----------------------------------------------------------------------

void txscreen (void)
{
  tonegen(1000,samplerate);
  tonegen(0,samplerate>>4);
  vis (VISCODE);

  for (int y = 0; y < 256; y += 2)
    {
      tonegen(1200, 160);
      tonegen(1500, 17);
      // for the "most noise resistance" (quoted because I haven't tested)
      doline(y);
      doline(y);
      doline(y+1);
      doline(y+1);
      // for the usual black-on-white matrix
      //doline(y);
      //tonegen(1900,scanlen);
      //tonegen(1900,scanlen);
      //doline(y+1);
    }
  tonegen(0,samplerate);
}

// ----------------------------------------------------------------------

void loop()
{
  txscreen();
  for (int i = 600; i; i--) delay(1000);
}

// ----------------------------------------------------------------------
